using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using Pasta.Medio.Abstract;
using Pasta.Medio.Configuration;
using Pasta.Medio.Domain;
using Pasta.Medio.Models;

namespace Pasta.Medio.Concrete
{
	public class EnumerateMedia1 : IEnumerateMedia
	{
		private const int ProgressInterval = 150;

		private BackgroundWorker _worker;
		private Stopwatch _watch = new Stopwatch();
		private ListMediaModel _model = null;
		private List<Recording> _catalog;
		private bool _restartWorker = false;
		private int _displayId = 1;

		// the following properties should be injected
		public Settings Settings { get; set; }
		public MedioRepository Repository { get; set; }

		// Worker Task and State
		public Enums.WorkerTask Task { get; private set; }
		public Enums.WorkerState State { get; private set; }
		public long ElapsedTimer { get { return _watch.ElapsedMilliseconds; } }
		public List<Error> Errors { get; private set; }

		public int CurrentId;
		public string CurrentSha1;

		// callback methods
		public ProgressChangedEventHandler ProgressChanged { get; set; }

		public EnumerateMedia1(ListMediaModel model)
		{
			_model = model;
			Settings = Program.Settings;
			Repository = Program.Repository;
			Initialize();

			ProgressChanged = null;
			_watch.Reset();

			Task = Enums.WorkerTask.List;
			State = Enums.WorkerState.Waiting;
			Errors = new List<Error>();
		}

		private void Initialize()
		{
			// initialize background worker
			_worker = new BackgroundWorker();
			_worker.WorkerReportsProgress = true;
			_worker.ProgressChanged += worker_ProgressChanged;
			_worker.WorkerSupportsCancellation = true;
			_worker.RunWorkerCompleted += worker_RunWorkerCompleted;
			_worker.DoWork += worker_DoWork;

			// cache the list of catalog recordings
			_catalog = Repository.List();
		}

		public bool Start()
		{
			if (!Directory.Exists(_model.Path))
			{
				AddError(ErrorType.MediaFile, Severity.Error, "Folder {0} does not exist", _model.Path);
				return false;
			}

			if (_worker.IsBusy)
			{
				_restartWorker = true;
				_worker.CancelAsync();
			}
			else
			{
				ResetModel();
				_worker.RunWorkerAsync();
				//worker_DoWork(this, new DoWorkEventArgs(null));
			}
			return true;
		}

		private void worker_DoWork(object sender, DoWorkEventArgs e)
		{
			State = Enums.WorkerState.Working;
			if (Task == Enums.WorkerTask.List)
			{
				ListFolders(_model.Path, e);
				if (!e.Cancel)
				{
					ListRecordings(e);
				}
				// sync mode
				//Task = Enums.WorkerTask.Analyse;
				//AnalyzeRecordings(e);
			}
			else if (Task == Enums.WorkerTask.Analyse)
			{
				if (!e.Cancel)
				{
					//using (var db = new MedioEntities())
					//{
					//var exist = from rec in db.Recordings
					//			join m in _model.Media on rec.Size equals m.Size
					//			where rec.TypeEnum == (int)m.Type
					//			select rec;
					//}

					//AnalyzeRecordings(e);
				}
			}
		}

		private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if (e.Error != null)
			{
				AddError(ErrorType.MediaFile, Severity.Error, e.Error.Message);
				Finish(Enums.WorkerState.Error);
			}
			else if (e.Cancelled)
			{
				if (_restartWorker)
				{
					Start();
				}
				else
				{
					Finish(Enums.WorkerState.Cancelled);
				}
			}
			else // thread complete
			{
				if (Task == Enums.WorkerTask.List)
				{
					// restart thread to analyze the recordings
					Task = Enums.WorkerTask.Analyse;
					_worker.RunWorkerAsync();
				}
				else
				{
					Finish(Enums.WorkerState.Complete);
				}
			}
		}

		private void Finish(Enums.WorkerState state)
		{
			State = state;
			worker_ProgressChanged(this, null);
		}

		private void ResetModel()
		{
			Task = Enums.WorkerTask.List;
			_model.Folders.Clear();
			_model.Media.Clear();
			_model.ImageCount = _model.VideoCount = 0;
			_model.NewImages = _model.NewVideos = 0;
			_displayId = 1;
			Errors.Clear();
			_restartWorker = false;
			_watch.Reset();
		}

		private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			if (ProgressChanged != null)
			{
				ProgressChanged(sender, e);
			}
		}

		private void ListFolders(string path, DoWorkEventArgs e)
		{
			try
			{
				var children = Directory.EnumerateDirectories(path).ToList();
				_model.Folders.Add(path);
				foreach (var child in children)
				{
					if (_worker.CancellationPending)
					{
						e.Cancel = true;
						return;
					}
					ListFolders(child, e);
				}
			}
			catch { }
		}

		private void ListRecordings(DoWorkEventArgs e)
		{
			int runner = 0;
			foreach (string folder in _model.Folders)
			{
				foreach (string filter in Settings.MediaTypes)
				{
					List<string> files = null;
					try
					{
						files = Directory.GetFiles(folder, filter, SearchOption.TopDirectoryOnly).ToList();
					}
					catch (Exception ex)
					{
						AddError(ErrorType.MediaFile, Severity.Error, "Error accessing folder {0}: {1}", folder, ex.Message);
						continue;
					}
					foreach (string file in files)
					{
						if (_worker.CancellationPending)
						{
							e.Cancel = true;
							return;
						}

						try
						{
							FileInfo fi = new FileInfo(file);
							MediaView mv = new MediaView();
							mv.DisplayId = _displayId++;
							bool success = IsImage(fi) ? GetImageInfo(fi, mv) : GetVideoInfo(fi, mv);
							//media.Camera = _drive.VolumeLabel;
							mv.Path = fi.FullName;
							mv.TargetName = CreateTargetName(fi);
							mv.Size = fi.Length;
							mv.Sha1 = "";
							_model.Media.Add(mv);

							// progress
							runner++;
							if (runner % ProgressInterval == 0)
							{
								_worker.ReportProgress(100 * runner / files.Count, _model);
							}
						}
						catch (Exception ex)
						{
							AddError(ErrorType.MediaFile, Severity.Error, "Error accessing file {0}: {1}", file, ex.Message);
						}
					}
					_worker.ReportProgress(100, _model);
				}
			}
		}

		private bool GetImageInfo(FileInfo fi, MediaView media)
		{
			bool success = false;
			if (fi != null && IsImage(fi))
			{
				_model.ImageCount++;
				media.Type = Enums.Type.Image;
				try
				{
					using (FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
					{
						BitmapSource img = BitmapFrame.Create(fs);
						BitmapMetadata md = (BitmapMetadata)img.Metadata;
						media.RecordedOn = DateTime.Parse(md.DateTaken);
						media.ModifiedOn = FileDate(fi);
						string[] parts = md.CameraModel.Split(new char[] { ',' });
						media.Camera = md.CameraManufacturer + " " + parts[parts.Length - 1].Trim();
						media.Size = fi.Length;
						success = true;
					}
				}
				catch { }
			}
			return success;
		}

		public bool GetVideoInfo(FileInfo fi, MediaView media)
		{
			bool success = false;
			if (fi != null && IsVideo(fi))
			{
				_model.VideoCount++;
				media.Type = Enums.Type.Video;
				try
				{
					using (FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
					{
						media.RecordedOn = FileDate(fi);
						media.Size = fi.Length;
						success = true;
					}
				}
				catch { }
			}
			return success;
		}

		public DateTime FileDate(FileInfo fi)
		{
			DateTime dt = fi.CreationTime;
			if (fi.LastAccessTime < dt) dt = fi.LastAccessTime;
			if (fi.LastWriteTime < dt) dt = fi.LastWriteTime;
			return dt;
		}

		public bool IsImage(FileInfo fi)
		{
			return fi != null && Settings.ImageTypesString.Contains(fi.Extension.Substring(1).ToLower());
		}

		public bool IsVideo(FileInfo fi)
		{
			return fi != null && Settings.VideoTypesString.Contains(fi.Extension.Substring(1).ToLower());
		}

		private void AnalyzeRecordings(DoWorkEventArgs e)
		{
			int runner = 0;
			var recs = Program.Repository.List();
			foreach (var rec in _model.Media)
			{
				if (_worker.CancellationPending)
				{
					e.Cancel = true;
					return;
				}

				try
				{
					rec.Sha1 = Util.ComputeSHA1(rec.Path);

					rec.InCatalog = Program.Context.Recordings.Any(r => r.Sha1 == rec.Sha1 && r.Size == rec.Size);
					if (!rec.InCatalog)
					{
						if (rec.Type == Enums.Type.Image)
							_model.NewImages++;
						else
							_model.NewVideos++;
					}

					runner++;
					_worker.ReportProgress(100 * runner / _model.Media.Count, rec);
				}
				catch (Exception ex)
				{
					AddError(ErrorType.MediaFile, Severity.Error, "Cannot open recording {0}: {1}", rec.Path, ex.Message);
					continue;
				}
			}
		}

		public void Cancel()
		{
			if (_worker.IsBusy)
			{
				_restartWorker = false;
				_worker.CancelAsync();
			}
		}

		private string CreateTargetName(FileInfo fi)
		{
			string name = fi.LastWriteTime.ToString(Settings.RenamePattern);
			int n = _model.Media.Count(m => m.RecordedOn == fi.CreationTime);
			if (n > 0)
			{
				name += String.Format(Settings.DuplicatePattern, n + 1);
			}
			name += fi.Extension;
			return name;
		}

		void AddError(ErrorType type, Severity severity, string message, params object[] args)
		{
			Errors.AddError(type, severity, message, args);
		}
	}

	/*
		DriveInfo[] drives = DriveInfo.GetDrives();
		foreach (var drive in drives.Where(d => d.IsReady))
		{
			bt.SharedProps.Caption = drive.Name;
		}

		public static IEnumerable<string> GetFiles(string path, string[] searchPatterns, SearchOption searchOption = SearchOption.TopDirectoryOnly)
		{
			return searchPatterns.AsParallel().SelectMany(searchPattern => Directory.EnumerateFiles(path, searchPattern, searchOption));
		}
	 
		private DriveInfo GetDriveInfo(char name)
		{
			DriveInfo[] drives = DriveInfo.GetDrives();
			return drives.Where(d => d.Name[0] == name).FirstOrDefault();
		}

	*/
}
